library(tidyverse)     # for data cleaning and plotting
library(googlesheets4) # for reading googlesheet data
library(lubridate)     # for date manipulation
library(openintro)     # for the abbr2state() function
library(palmerpenguins)# for Palmer penguin data
library(maps)          # for map data
library(ggmap)         # for mapping points on maps
library(gplots)        # for col2hex() function
library(RColorBrewer)  # for color palettes
library(sf)            # for working with spatial data
library(leaflet)       # for highly customizable mapping
library(ggthemes)      # for more themes (including theme_map())
library(plotly)        # for the ggplotly() - basic interactivity
library(gganimate)     # for adding animation layers to ggplots
library(transformr)    # for "tweening" (gganimate)
library(shiny)  # for creating interactive apps
library(gifski)
library(ggridges)
library(ggimage)
gs4_deauth()           # To not have to authorize each time you knit.
theme_set(theme_minimal())
# SNCF Train data
small_trains <- read_csv("https://raw.githubusercontent.com/rfordatascience/tidytuesday/master/data/2019/2019-02-26/small_trains.csv") 

# Lisa's garden data
garden_harvest <- read_sheet("https://docs.google.com/spreadsheets/d/1DekSazCzKqPS2jnGhKue7tLxRU3GVL1oxi-4bEM5IWw/edit?usp=sharing") %>% 
  mutate(date = ymd(date))

# Lisa's Mallorca cycling data
mallorca_bike_day7 <- read_csv("https://www.dropbox.com/s/zc6jan4ltmjtvy0/mallorca_bike_day7.csv?dl=1") %>% 
  select(1:4, speed)

# Heather Lendway's Ironman 70.3 Pan Am championships Panama data
panama_swim <- read_csv("https://raw.githubusercontent.com/llendway/gps-data/master/data/panama_swim_20160131.csv")

panama_bike <- read_csv("https://raw.githubusercontent.com/llendway/gps-data/master/data/panama_bike_20160131.csv")

panama_run <- read_csv("https://raw.githubusercontent.com/llendway/gps-data/master/data/panama_run_20160131.csv")

#COVID-19 data from the New York Times
covid19 <- read_csv("https://raw.githubusercontent.com/nytimes/covid-19-data/master/us-states.csv")
  • NEW!! With animated graphs, add eval=FALSE to the code chunk that creates the animation and saves it using anim_save(). Add another code chunk to reread the gif back into the file. See the tutorial for help.

  • When you are finished with ALL the exercises, uncomment the options at the top so your document looks nicer. Don’t do it before then, or else you might miss some important warnings and messages.

Warm-up exercises from tutorial

  1. Choose 2 graphs you have created for ANY assignment in this class and add interactivity using the ggplotly() function.
variety_garden_graph <- garden_harvest %>%
  filter(vegetable %in% c("lettuce")) %>%
  arrange(desc(variety)) %>%
  group_by(variety) %>%
  summarize(n = n()) %>%
  ggplot(aes(x = n, y = fct_reorder(variety, n), text = variety)) +
  geom_col(fill = "black", color = "yellow") +
  labs(title = "Harvest of Different Varieties of Lettuce",
       x = "Count",
       y = "Variety")

ggplotly(variety_garden_graph,
         tooltip = c("text", "x"))
veggie_harvest_graph2 <- garden_harvest %>% 
  group_by(vegetable) %>% 
  summarize(total_wt_lbs = sum(weight)*0.00220462) %>% 
  ggplot() +
  geom_col(aes(x = total_wt_lbs, 
               y = fct_reorder(vegetable, 
                               total_wt_lbs, 
                               .desc = FALSE),
               text = vegetable)) +
  labs(title = "Total Harvest by vegetable (lb)", 
       x = "Count",
       y = "")

ggplotly(veggie_harvest_graph2,
         tooltip = c("text", "x"))
  1. Use animation to tell an interesting story with the small_trains dataset that contains data from the SNCF (National Society of French Railways). These are Tidy Tuesday data! Read more about it here.
delay_small_trains <- small_trains %>%
  group_by(service) %>%
  filter(!is.na(service)) %>%
  filter(departure_station %in% c("PARIS EST", "PARIS LYON"))

delay3 <- delay_small_trains %>% 
  ggplot(aes(x = avg_delay_all_departing, 
             y = service)) +
  geom_density_ridges() +
  transition_states(year) +
  labs(title = "Departing Delay by French Train Services",
       x = "Average Delay for Departing Trains (Minutes)",
       y = "Train Service",
       subtitle = "Moving to {next_state}") 
anim_save("trains2.gif", delay3)

Garden data

  1. In this exercise, you will create a stacked area plot that reveals itself over time (see the geom_area() examples here). You will look at cumulative harvest of tomato varieties over time. You should do the following:
  • From the garden_harvest data, filter the data to the tomatoes and find the daily harvest in pounds for each variety.
  • Then, for each variety, find the cumulative harvest in pounds.
  • Use the data you just made to create a static cumulative harvest area plot, with the areas filled with different colors for each vegetable and arranged (HINT: fct_reorder()) from most to least harvested (most on the bottom).
  • Add animation to reveal the plot over date.
garden_harvest %>%
  filter(vegetable == "tomatoes") %>%
  complete(variety, date = seq.Date(min(date), max(date), by="day")) %>%
  select(-c(vegetable, units)) %>%
  mutate(weight = replace_na(weight, 0)) %>%
  group_by(variety, date) %>%
  summarize(daily_harvest_lb = sum(weight)*0.00220462) %>%
  mutate(cumsum_daily_harvest_lb = cumsum(daily_harvest_lb)) %>%
  select(-daily_harvest_lb) %>%
  ggplot() +
    geom_area(aes(x = date, y = cumsum_daily_harvest_lb, fill = variety), position = position_stack()) +
    transition_reveal(date) +
    labs(title = "Cumulative Harvest of Tomatoe Variety over Time",
       x = "Date",
       y = "Cumulative Daily Harvest (Lb)",
       subtitle = "Moving to {frame_along}") 
anim_save("harvest1.gif")

Maps, animation, and movement!

  1. Map my mallorca_bike_day7 bike ride using animation! Requirements:
  • Plot on a map using ggmap.
  • Show “current” location with a red point.
  • Show path up until the current point.
  • Color the path according to elevation.
  • Show the time in the subtitle.
  • CHALLENGE: use the ggimage package and geom_image to add a bike image instead of a red point. You can use this image. See here for an example.
  • Add something of your own! And comment on if you prefer this to the static map and why or why not.
bike_image_link <- "https://raw.githubusercontent.com/llendway/animation_and_interactivity/master/bike.png"

mallorca_bike_day7 <- mallorca_bike_day7 %>%
  mutate(image = bike_image_link)

mallorca_map <- get_stamenmap(
    bbox = c(left = 2.28, bottom = 39.41, right = 3.03, top = 39.8), 
    maptype = "terrain",
    zoom = 11
)
ggmap(mallorca_map) +
  geom_point(data = mallorca_bike_day7, 
             aes(x = lon, y = lat),
             color = "red", size = .5) +
  geom_path(data = mallorca_bike_day7, 
             aes(x = lon, y = lat, color = ele),
             size = .5) +
  labs(title = "Mallorca Bike Trail",
       subtitle = "Time: {frame_along}") +
  geom_image(data = mallorca_bike_day7,
            aes(x = lon, y = lat, image = bike_image_link), 
            size = 0.075) +
  transition_reveal(time) +
  scale_color_viridis_c(option = "magma") +
  theme_map() +
  theme(legend.background = element_blank())
anim_save("bike1.gif")

I personally prefer this animated map over the static map as there are details that we are able to observe on the animated map, such as the direction of the bike ride and the bike that represents Lisa on her bike. Lastly, doesn’t almost any animated plot look better than a static plot? At least in my opinion.

  1. In this exercise, you get to meet my sister, Heather! She is a proud Mac grad, currently works as a Data Scientist at 3M where she uses R everyday, and for a few years (while still holding a full-time job) she was a pro triathlete. You are going to map one of her races. The data from each discipline of the Ironman 70.3 Pan Am championships, Panama is in a separate file - panama_swim, panama_bike, and panama_run. Create a similar map to the one you created with my cycling data. You will need to make some small changes: 1. combine the files (HINT: bind_rows(), 2. make the leading dot a different color depending on the event (for an extra challenge, make it a different image using `geom_image()!), 3. CHALLENGE (optional): color by speed, which you will need to compute on your own from the data. You can read Heather’s race report here. She is also in the Macalester Athletics Hall of Fame and still has records at the pool.
total_trail <- panama_swim %>%
  bind_rows(list(panama_run, panama_bike)) 
  
panama_map <- get_stamenmap(
  bbox = c(left = -79.56, bottom = 8.88, right = -79.41, top = 9.001),
  maptype = "terrain",
  zoom = 13
)

ggmap(panama_map) +
  geom_point(data = total_trail, 
             aes(x = lon, y = lat, color = event, shape = event),
             size = 2) +
  geom_path(data = total_trail,
            aes(x = lon, y = lat, color = event),
            alpha = 0.8, size = 0.5) +
  labs(title = "Ironman 70.3 Pan Am Championship",
       subtitle = "Time: {frame_along}") +
  scale_color_viridis_d(option = "magma") +
  theme_map() +
  theme(legend.background = element_blank()) +
  transition_reveal(time)
anim_save("panama.gif")

COVID-19 data

  1. In this exercise, you are going to replicate many of the features in this visualization by Aitish Bhatia but include all US states. Requirements:
  • Create a new variable that computes the number of new cases in the past week (HINT: use the lag() function you’ve used in a previous set of exercises). Replace missing values with 0’s using replace_na().
  • Filter the data to omit rows where the cumulative case counts are less than 20.
  • Create a static plot with cumulative cases on the x-axis and new cases in the past 7 days on the x-axis. Connect the points for each state over time. HINTS: use geom_path() and add a group aesthetic. Put the x and y axis on the log scale and make the tick labels look nice - scales::comma is one option. This plot will look pretty ugly as is.
  • Animate the plot to reveal the pattern by date. Display the date as the subtitle. Add a leading point to each state’s line (geom_point()) and add the state name as a label (geom_text() - you should look at the check_overlap argument).
  • Use the animate() function to have 200 frames in your animation and make it 30 seconds long.
  • Comment on what you observe.
covid19 %>%
  group_by(state) %>%
  mutate(lag7 = lag(cases, 7, order_by = date)) %>%
  replace_na(list(lag7 = 0)) %>%
  mutate(new_cases_past_week = cases - lag7) %>%
  filter(cases >= 20) %>%
  
  ggplot(aes(x = cases, y = new_cases_past_week, group = state)) +
  geom_point(color = "red") +
  geom_path(color = "light blue") +
  geom_text(aes(label = state), check_overlap = TRUE) +
  scale_x_log10(labels = scales::comma) +
  scale_y_log10(labels = scales::comma) +
  labs(
    title = "Trajectory of US COVID-19 Confirmed Cases",
    x = "Total Confirmed Cases",
    y = "New Confirmed Cases (in the Past Week)",
    subtitle = "Date: {frame_along}"
  ) +
  theme(legend.position = "none") +
  transition_reveal(date) -> covid19trajectory_gganim

animate(covid19trajectory_gganim,
        nframes = 200,
        duration = 30)
anim_save("covid2.gif")

There is a lot to observe in this visualization as all states have been included. Once again, this plot very clearly shows the drastic surge in COVID-19 cases in New York and New Jersey at the start of the pandemic. We also observe how a state like Vermont does extremely well in dealing with COVID-19 as their new confirmed cases in a week drop quickly in June, then see an increase again at the end of June and fall again at the start of August. Furthermore, their total number of confirmed cases is extremely low, around 1,200 people during the entire pandemic. Additionally, we are also able to observe the rapid increase of cases in both Florida and Texas in July going through both August and September.

  1. In this exercise you will animate a map of the US, showing how cumulative COVID-19 cases per 10,000 residents has changed over time. This is similar to exercises 11 & 12 from the previous exercises, with the added animation! So, in the end, you should have something like the static map you made there, but animated over all the days. Put date in the subtitle. Comment on what you see.
census_pop_est_2018 <- read_csv("https://www.dropbox.com/s/6txwv3b4ng7pepe/us_census_2018_state_pop_est.csv?dl=1") %>% 
  separate(state, into = c("dot","state"), extra = "merge") %>% 
  select(-dot) %>% 
  mutate(state = str_to_lower(state))
covid19_population <-
  covid19 %>% 
  mutate(state = str_to_lower(state)) %>%
  left_join(census_pop_est_2018,
            by = "state") %>% 
  group_by(state, est_pop_2018, date) %>%
  summarize(cumulative_cases = max(cases)) %>%
  mutate(cases_per_10000 = (cumulative_cases/est_pop_2018)*10000)

states_map <- map_data("state")

covid_map <- covid19_population %>% 
  mutate(state = str_to_lower(state), weekday = wday(date, label=TRUE)) %>%
  filter(weekday == "Fri") %>%
  ggplot() +
  geom_map(map = states_map,
           aes(map_id = state, fill = cases_per_10000, group = date)) +
  expand_limits(x = states_map$long, y = states_map$lat) + 
  labs(title = "Cumulative COVID-19 cases per 10,000 people in the United States") +
  theme(legend.background = element_blank()) + 
  theme_map() +
  scale_fill_viridis_c() +
  transition_states(date, transition_length = 0) +
  labs(subtitle = "Moving to {next_state}")

animate(covid_map, duration = 30)
anim_save("covid.gif", covid_map)

We clearly see the sad reality of COVID-19 in the United States. It starts off with a rapidly rising number of cases per 10,000 people in New York, Lousiana, and Arizona. Shortly after, the whole country follows with almost every state having more than 300 COVID-19 cases per state except for a couple states, such as Vermont, Maine, and Oregon. We ultimately see the United States light up in green and yellow which means that almost every state has close to 200-300 cases per 10,000 people.

LS0tCnRpdGxlOiAnV2Vla2x5IEV4ZXJjaXNlcyAjNScKYXV0aG9yOiAiRmxveWQgS3JvbSIKb3V0cHV0OiAKICBodG1sX2RvY3VtZW50OgogICAga2VlcF9tZDogVFJVRQogICAgdG9jOiBUUlVFCiAgICB0b2NfZmxvYXQ6IFRSVUUKICAgIGRmX3ByaW50OiBwYWdlZAogICAgdGhlbWU6IGpvdXJuYWwKICAgIGNvZGVfZG93bmxvYWQ6IHRydWUKLS0tCgoKYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9CmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSwgZXJyb3I9VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSkKYGBgCgpgYGB7ciBsaWJyYXJpZXN9CmxpYnJhcnkodGlkeXZlcnNlKSAgICAgIyBmb3IgZGF0YSBjbGVhbmluZyBhbmQgcGxvdHRpbmcKbGlicmFyeShnb29nbGVzaGVldHM0KSAjIGZvciByZWFkaW5nIGdvb2dsZXNoZWV0IGRhdGEKbGlicmFyeShsdWJyaWRhdGUpICAgICAjIGZvciBkYXRlIG1hbmlwdWxhdGlvbgpsaWJyYXJ5KG9wZW5pbnRybykgICAgICMgZm9yIHRoZSBhYmJyMnN0YXRlKCkgZnVuY3Rpb24KbGlicmFyeShwYWxtZXJwZW5ndWlucykjIGZvciBQYWxtZXIgcGVuZ3VpbiBkYXRhCmxpYnJhcnkobWFwcykgICAgICAgICAgIyBmb3IgbWFwIGRhdGEKbGlicmFyeShnZ21hcCkgICAgICAgICAjIGZvciBtYXBwaW5nIHBvaW50cyBvbiBtYXBzCmxpYnJhcnkoZ3Bsb3RzKSAgICAgICAgIyBmb3IgY29sMmhleCgpIGZ1bmN0aW9uCmxpYnJhcnkoUkNvbG9yQnJld2VyKSAgIyBmb3IgY29sb3IgcGFsZXR0ZXMKbGlicmFyeShzZikgICAgICAgICAgICAjIGZvciB3b3JraW5nIHdpdGggc3BhdGlhbCBkYXRhCmxpYnJhcnkobGVhZmxldCkgICAgICAgIyBmb3IgaGlnaGx5IGN1c3RvbWl6YWJsZSBtYXBwaW5nCmxpYnJhcnkoZ2d0aGVtZXMpICAgICAgIyBmb3IgbW9yZSB0aGVtZXMgKGluY2x1ZGluZyB0aGVtZV9tYXAoKSkKbGlicmFyeShwbG90bHkpICAgICAgICAjIGZvciB0aGUgZ2dwbG90bHkoKSAtIGJhc2ljIGludGVyYWN0aXZpdHkKbGlicmFyeShnZ2FuaW1hdGUpICAgICAjIGZvciBhZGRpbmcgYW5pbWF0aW9uIGxheWVycyB0byBnZ3Bsb3RzCmxpYnJhcnkodHJhbnNmb3JtcikgICAgIyBmb3IgInR3ZWVuaW5nIiAoZ2dhbmltYXRlKQpsaWJyYXJ5KHNoaW55KSAgIyBmb3IgY3JlYXRpbmcgaW50ZXJhY3RpdmUgYXBwcwpsaWJyYXJ5KGdpZnNraSkKbGlicmFyeShnZ3JpZGdlcykKbGlicmFyeShnZ2ltYWdlKQpnczRfZGVhdXRoKCkgICAgICAgICAgICMgVG8gbm90IGhhdmUgdG8gYXV0aG9yaXplIGVhY2ggdGltZSB5b3Uga25pdC4KdGhlbWVfc2V0KHRoZW1lX21pbmltYWwoKSkKYGBgCgpgYGB7ciBkYXRhfQojIFNOQ0YgVHJhaW4gZGF0YQpzbWFsbF90cmFpbnMgPC0gcmVhZF9jc3YoImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9yZm9yZGF0YXNjaWVuY2UvdGlkeXR1ZXNkYXkvbWFzdGVyL2RhdGEvMjAxOS8yMDE5LTAyLTI2L3NtYWxsX3RyYWlucy5jc3YiKSAKCiMgTGlzYSdzIGdhcmRlbiBkYXRhCmdhcmRlbl9oYXJ2ZXN0IDwtIHJlYWRfc2hlZXQoImh0dHBzOi8vZG9jcy5nb29nbGUuY29tL3NwcmVhZHNoZWV0cy9kLzFEZWtTYXpDektxUFMyam5HaEt1ZTd0THhSVTNHVkwxb3hpLTRiRU01SVd3L2VkaXQ/dXNwPXNoYXJpbmciKSAlPiUgCiAgbXV0YXRlKGRhdGUgPSB5bWQoZGF0ZSkpCgojIExpc2EncyBNYWxsb3JjYSBjeWNsaW5nIGRhdGEKbWFsbG9yY2FfYmlrZV9kYXk3IDwtIHJlYWRfY3N2KCJodHRwczovL3d3dy5kcm9wYm94LmNvbS9zL3pjNmphbjRsdG1qdHZ5MC9tYWxsb3JjYV9iaWtlX2RheTcuY3N2P2RsPTEiKSAlPiUgCiAgc2VsZWN0KDE6NCwgc3BlZWQpCgojIEhlYXRoZXIgTGVuZHdheSdzIElyb25tYW4gNzAuMyBQYW4gQW0gY2hhbXBpb25zaGlwcyBQYW5hbWEgZGF0YQpwYW5hbWFfc3dpbSA8LSByZWFkX2NzdigiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL2xsZW5kd2F5L2dwcy1kYXRhL21hc3Rlci9kYXRhL3BhbmFtYV9zd2ltXzIwMTYwMTMxLmNzdiIpCgpwYW5hbWFfYmlrZSA8LSByZWFkX2NzdigiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL2xsZW5kd2F5L2dwcy1kYXRhL21hc3Rlci9kYXRhL3BhbmFtYV9iaWtlXzIwMTYwMTMxLmNzdiIpCgpwYW5hbWFfcnVuIDwtIHJlYWRfY3N2KCJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vbGxlbmR3YXkvZ3BzLWRhdGEvbWFzdGVyL2RhdGEvcGFuYW1hX3J1bl8yMDE2MDEzMS5jc3YiKQoKI0NPVklELTE5IGRhdGEgZnJvbSB0aGUgTmV3IFlvcmsgVGltZXMKY292aWQxOSA8LSByZWFkX2NzdigiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL255dGltZXMvY292aWQtMTktZGF0YS9tYXN0ZXIvdXMtc3RhdGVzLmNzdiIpCgpgYGAKKiAqKk5FVyEhKiogV2l0aCBhbmltYXRlZCBncmFwaHMsIGFkZCBgZXZhbD1GQUxTRWAgdG8gdGhlIGNvZGUgY2h1bmsgdGhhdCBjcmVhdGVzIHRoZSBhbmltYXRpb24gYW5kIHNhdmVzIGl0IHVzaW5nIGBhbmltX3NhdmUoKWAuIEFkZCBhbm90aGVyIGNvZGUgY2h1bmsgdG8gcmVyZWFkIHRoZSBnaWYgYmFjayBpbnRvIHRoZSBmaWxlLiBTZWUgdGhlIFt0dXRvcmlhbF0oaHR0cHM6Ly9hbmltYXRpb24tYW5kLWludGVyYWN0aXZpdHktaW4tci5uZXRsaWZ5LmFwcC8pIGZvciBoZWxwLiAKCiogV2hlbiB5b3UgYXJlIGZpbmlzaGVkIHdpdGggQUxMIHRoZSBleGVyY2lzZXMsIHVuY29tbWVudCB0aGUgb3B0aW9ucyBhdCB0aGUgdG9wIHNvIHlvdXIgZG9jdW1lbnQgbG9va3MgbmljZXIuIERvbid0IGRvIGl0IGJlZm9yZSB0aGVuLCBvciBlbHNlIHlvdSBtaWdodCBtaXNzIHNvbWUgaW1wb3J0YW50IHdhcm5pbmdzIGFuZCBtZXNzYWdlcy4KCiMjIFdhcm0tdXAgZXhlcmNpc2VzIGZyb20gdHV0b3JpYWwKCiAgMS4gQ2hvb3NlIDIgZ3JhcGhzIHlvdSBoYXZlIGNyZWF0ZWQgZm9yIEFOWSBhc3NpZ25tZW50IGluIHRoaXMgY2xhc3MgYW5kIGFkZCBpbnRlcmFjdGl2aXR5IHVzaW5nIHRoZSBgZ2dwbG90bHkoKWAgZnVuY3Rpb24uCiAgCmBgYHtyfQp2YXJpZXR5X2dhcmRlbl9ncmFwaCA8LSBnYXJkZW5faGFydmVzdCAlPiUKICBmaWx0ZXIodmVnZXRhYmxlICVpbiUgYygibGV0dHVjZSIpKSAlPiUKICBhcnJhbmdlKGRlc2ModmFyaWV0eSkpICU+JQogIGdyb3VwX2J5KHZhcmlldHkpICU+JQogIHN1bW1hcml6ZShuID0gbigpKSAlPiUKICBnZ3Bsb3QoYWVzKHggPSBuLCB5ID0gZmN0X3Jlb3JkZXIodmFyaWV0eSwgbiksIHRleHQgPSB2YXJpZXR5KSkgKwogIGdlb21fY29sKGZpbGwgPSAiYmxhY2siLCBjb2xvciA9ICJ5ZWxsb3ciKSArCiAgbGFicyh0aXRsZSA9ICJIYXJ2ZXN0IG9mIERpZmZlcmVudCBWYXJpZXRpZXMgb2YgTGV0dHVjZSIsCiAgICAgICB4ID0gIkNvdW50IiwKICAgICAgIHkgPSAiVmFyaWV0eSIpCgpnZ3Bsb3RseSh2YXJpZXR5X2dhcmRlbl9ncmFwaCwKICAgICAgICAgdG9vbHRpcCA9IGMoInRleHQiLCAieCIpKQpgYGAKYGBge3J9CnZlZ2dpZV9oYXJ2ZXN0X2dyYXBoMiA8LSBnYXJkZW5faGFydmVzdCAlPiUgCiAgZ3JvdXBfYnkodmVnZXRhYmxlKSAlPiUgCiAgc3VtbWFyaXplKHRvdGFsX3d0X2xicyA9IHN1bSh3ZWlnaHQpKjAuMDAyMjA0NjIpICU+JSAKICBnZ3Bsb3QoKSArCiAgZ2VvbV9jb2woYWVzKHggPSB0b3RhbF93dF9sYnMsIAogICAgICAgICAgICAgICB5ID0gZmN0X3Jlb3JkZXIodmVnZXRhYmxlLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRvdGFsX3d0X2xicywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAuZGVzYyA9IEZBTFNFKSwKICAgICAgICAgICAgICAgdGV4dCA9IHZlZ2V0YWJsZSkpICsKICBsYWJzKHRpdGxlID0gIlRvdGFsIEhhcnZlc3QgYnkgdmVnZXRhYmxlIChsYikiLCAKICAgICAgIHggPSAiQ291bnQiLAogICAgICAgeSA9ICIiKQoKZ2dwbG90bHkodmVnZ2llX2hhcnZlc3RfZ3JhcGgyLAogICAgICAgICB0b29sdGlwID0gYygidGV4dCIsICJ4IikpCmBgYAogIAogIDIuIFVzZSBhbmltYXRpb24gdG8gdGVsbCBhbiBpbnRlcmVzdGluZyBzdG9yeSB3aXRoIHRoZSBgc21hbGxfdHJhaW5zYCBkYXRhc2V0IHRoYXQgY29udGFpbnMgZGF0YSBmcm9tIHRoZSBTTkNGIChOYXRpb25hbCBTb2NpZXR5IG9mIEZyZW5jaCBSYWlsd2F5cykuIFRoZXNlIGFyZSBUaWR5IFR1ZXNkYXkgZGF0YSEgUmVhZCBtb3JlIGFib3V0IGl0IFtoZXJlXShodHRwczovL2dpdGh1Yi5jb20vcmZvcmRhdGFzY2llbmNlL3RpZHl0dWVzZGF5L3RyZWUvbWFzdGVyL2RhdGEvMjAxOS8yMDE5LTAyLTI2KS4KCmBgYHtyLCBldmFsPUZBTFNFfQpkZWxheV9zbWFsbF90cmFpbnMgPC0gc21hbGxfdHJhaW5zICU+JQogIGdyb3VwX2J5KHNlcnZpY2UpICU+JQogIGZpbHRlcighaXMubmEoc2VydmljZSkpICU+JQogIGZpbHRlcihkZXBhcnR1cmVfc3RhdGlvbiAlaW4lIGMoIlBBUklTIEVTVCIsICJQQVJJUyBMWU9OIikpCgpkZWxheTMgPC0gZGVsYXlfc21hbGxfdHJhaW5zICU+JSAKICBnZ3Bsb3QoYWVzKHggPSBhdmdfZGVsYXlfYWxsX2RlcGFydGluZywgCiAgICAgICAgICAgICB5ID0gc2VydmljZSkpICsKICBnZW9tX2RlbnNpdHlfcmlkZ2VzKCkgKwogIHRyYW5zaXRpb25fc3RhdGVzKHllYXIpICsKICBsYWJzKHRpdGxlID0gIkRlcGFydGluZyBEZWxheSBieSBGcmVuY2ggVHJhaW4gU2VydmljZXMiLAogICAgICAgeCA9ICJBdmVyYWdlIERlbGF5IGZvciBEZXBhcnRpbmcgVHJhaW5zIChNaW51dGVzKSIsCiAgICAgICB5ID0gIlRyYWluIFNlcnZpY2UiLAogICAgICAgc3VidGl0bGUgPSAiTW92aW5nIHRvIHtuZXh0X3N0YXRlfSIpIApgYGAKCmBgYHtyLCBldmFsPUZBTFNFfQphbmltX3NhdmUoInRyYWluczIuZ2lmIiwgZGVsYXkzKQpgYGAKCmBgYHtyLCBlY2hvPUZBTFNFfQprbml0cjo6aW5jbHVkZV9ncmFwaGljcygidHJhaW5zMi5naWYiKQpgYGAKCiMjIEdhcmRlbiBkYXRhCgogIDMuIEluIHRoaXMgZXhlcmNpc2UsIHlvdSB3aWxsIGNyZWF0ZSBhIHN0YWNrZWQgYXJlYSBwbG90IHRoYXQgcmV2ZWFscyBpdHNlbGYgb3ZlciB0aW1lIChzZWUgdGhlIGBnZW9tX2FyZWEoKWAgZXhhbXBsZXMgW2hlcmVdKGh0dHBzOi8vZ2dwbG90Mi50aWR5dmVyc2Uub3JnL3JlZmVyZW5jZS9wb3NpdGlvbl9zdGFjay5odG1sKSkuIFlvdSB3aWxsIGxvb2sgYXQgY3VtdWxhdGl2ZSBoYXJ2ZXN0IG9mIHRvbWF0byB2YXJpZXRpZXMgb3ZlciB0aW1lLiBZb3Ugc2hvdWxkIGRvIHRoZSBmb2xsb3dpbmc6CiAgKiBGcm9tIHRoZSBgZ2FyZGVuX2hhcnZlc3RgIGRhdGEsIGZpbHRlciB0aGUgZGF0YSB0byB0aGUgdG9tYXRvZXMgYW5kIGZpbmQgdGhlICpkYWlseSogaGFydmVzdCBpbiBwb3VuZHMgZm9yIGVhY2ggdmFyaWV0eS4gIAogICogVGhlbiwgZm9yIGVhY2ggdmFyaWV0eSwgZmluZCB0aGUgY3VtdWxhdGl2ZSBoYXJ2ZXN0IGluIHBvdW5kcy4gIAogICogVXNlIHRoZSBkYXRhIHlvdSBqdXN0IG1hZGUgdG8gY3JlYXRlIGEgc3RhdGljIGN1bXVsYXRpdmUgaGFydmVzdCBhcmVhIHBsb3QsIHdpdGggdGhlIGFyZWFzIGZpbGxlZCB3aXRoIGRpZmZlcmVudCBjb2xvcnMgZm9yIGVhY2ggdmVnZXRhYmxlIGFuZCBhcnJhbmdlZCAoSElOVDogYGZjdF9yZW9yZGVyKClgKSBmcm9tIG1vc3QgdG8gbGVhc3QgaGFydmVzdGVkIChtb3N0IG9uIHRoZSBib3R0b20pLiAgCiAgKiBBZGQgYW5pbWF0aW9uIHRvIHJldmVhbCB0aGUgcGxvdCBvdmVyIGRhdGUuIAogIApgYGB7ciwgZXZhbD1GQUxTRX0KZ2FyZGVuX2hhcnZlc3QgJT4lCiAgZmlsdGVyKHZlZ2V0YWJsZSA9PSAidG9tYXRvZXMiKSAlPiUKICBjb21wbGV0ZSh2YXJpZXR5LCBkYXRlID0gc2VxLkRhdGUobWluKGRhdGUpLCBtYXgoZGF0ZSksIGJ5PSJkYXkiKSkgJT4lCiAgc2VsZWN0KC1jKHZlZ2V0YWJsZSwgdW5pdHMpKSAlPiUKICBtdXRhdGUod2VpZ2h0ID0gcmVwbGFjZV9uYSh3ZWlnaHQsIDApKSAlPiUKICBncm91cF9ieSh2YXJpZXR5LCBkYXRlKSAlPiUKICBzdW1tYXJpemUoZGFpbHlfaGFydmVzdF9sYiA9IHN1bSh3ZWlnaHQpKjAuMDAyMjA0NjIpICU+JQogIG11dGF0ZShjdW1zdW1fZGFpbHlfaGFydmVzdF9sYiA9IGN1bXN1bShkYWlseV9oYXJ2ZXN0X2xiKSkgJT4lCiAgc2VsZWN0KC1kYWlseV9oYXJ2ZXN0X2xiKSAlPiUKICBnZ3Bsb3QoKSArCiAgICBnZW9tX2FyZWEoYWVzKHggPSBkYXRlLCB5ID0gY3Vtc3VtX2RhaWx5X2hhcnZlc3RfbGIsIGZpbGwgPSB2YXJpZXR5KSwgcG9zaXRpb24gPSBwb3NpdGlvbl9zdGFjaygpKSArCiAgICB0cmFuc2l0aW9uX3JldmVhbChkYXRlKSArCiAgICBsYWJzKHRpdGxlID0gIkN1bXVsYXRpdmUgSGFydmVzdCBvZiBUb21hdG9lIFZhcmlldHkgb3ZlciBUaW1lIiwKICAgICAgIHggPSAiRGF0ZSIsCiAgICAgICB5ID0gIkN1bXVsYXRpdmUgRGFpbHkgSGFydmVzdCAoTGIpIiwKICAgICAgIHN1YnRpdGxlID0gIk1vdmluZyB0byB7ZnJhbWVfYWxvbmd9IikgCmBgYAogIApgYGB7ciwgZXZhbD1GQUxTRX0KYW5pbV9zYXZlKCJoYXJ2ZXN0MS5naWYiKQpgYGAKICAKYGBge3IsIGVjaG89RkFMU0V9CmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKCJoYXJ2ZXN0MS5naWYiKQpgYGAKCiMjIE1hcHMsIGFuaW1hdGlvbiwgYW5kIG1vdmVtZW50IQoKICA0LiBNYXAgbXkgYG1hbGxvcmNhX2Jpa2VfZGF5N2AgYmlrZSByaWRlIHVzaW5nIGFuaW1hdGlvbiEgCiAgUmVxdWlyZW1lbnRzOgogICogUGxvdCBvbiBhIG1hcCB1c2luZyBgZ2dtYXBgLiAgCiAgKiBTaG93ICJjdXJyZW50IiBsb2NhdGlvbiB3aXRoIGEgcmVkIHBvaW50LiAKICAqIFNob3cgcGF0aCB1cCB1bnRpbCB0aGUgY3VycmVudCBwb2ludC4gIAogICogQ29sb3IgdGhlIHBhdGggYWNjb3JkaW5nIHRvIGVsZXZhdGlvbi4gIAogICogU2hvdyB0aGUgdGltZSBpbiB0aGUgc3VidGl0bGUuICAKICAqIENIQUxMRU5HRTogdXNlIHRoZSBgZ2dpbWFnZWAgcGFja2FnZSBhbmQgYGdlb21faW1hZ2VgIHRvIGFkZCBhIGJpa2UgaW1hZ2UgaW5zdGVhZCBvZiBhIHJlZCBwb2ludC4gWW91IGNhbiB1c2UgW3RoaXNdKGh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9sbGVuZHdheS9hbmltYXRpb25fYW5kX2ludGVyYWN0aXZpdHkvbWFzdGVyL2Jpa2UucG5nKSBpbWFnZS4gU2VlIFtoZXJlXShodHRwczovL2dvb2Rla2F0LmdpdGh1Yi5pby9wcmVzZW50YXRpb25zLzIwMTktaXN1Z2ctZ2dhbmltYXRlLXNwb29reS9zbGlkZXMuaHRtbCMzNSkgZm9yIGFuIGV4YW1wbGUuIAogICogQWRkIHNvbWV0aGluZyBvZiB5b3VyIG93biEgQW5kIGNvbW1lbnQgb24gaWYgeW91IHByZWZlciB0aGlzIHRvIHRoZSBzdGF0aWMgbWFwIGFuZCB3aHkgb3Igd2h5IG5vdC4KICAKYGBge3IsIGV2YWw9RkFMU0V9CmJpa2VfaW1hZ2VfbGluayA8LSAiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL2xsZW5kd2F5L2FuaW1hdGlvbl9hbmRfaW50ZXJhY3Rpdml0eS9tYXN0ZXIvYmlrZS5wbmciCgptYWxsb3JjYV9iaWtlX2RheTcgPC0gbWFsbG9yY2FfYmlrZV9kYXk3ICU+JQogIG11dGF0ZShpbWFnZSA9IGJpa2VfaW1hZ2VfbGluaykKCm1hbGxvcmNhX21hcCA8LSBnZXRfc3RhbWVubWFwKAogICAgYmJveCA9IGMobGVmdCA9IDIuMjgsIGJvdHRvbSA9IDM5LjQxLCByaWdodCA9IDMuMDMsIHRvcCA9IDM5LjgpLCAKICAgIG1hcHR5cGUgPSAidGVycmFpbiIsCiAgICB6b29tID0gMTEKKQpnZ21hcChtYWxsb3JjYV9tYXApICsKICBnZW9tX3BvaW50KGRhdGEgPSBtYWxsb3JjYV9iaWtlX2RheTcsIAogICAgICAgICAgICAgYWVzKHggPSBsb24sIHkgPSBsYXQpLAogICAgICAgICAgICAgY29sb3IgPSAicmVkIiwgc2l6ZSA9IC41KSArCiAgZ2VvbV9wYXRoKGRhdGEgPSBtYWxsb3JjYV9iaWtlX2RheTcsIAogICAgICAgICAgICAgYWVzKHggPSBsb24sIHkgPSBsYXQsIGNvbG9yID0gZWxlKSwKICAgICAgICAgICAgIHNpemUgPSAuNSkgKwogIGxhYnModGl0bGUgPSAiTWFsbG9yY2EgQmlrZSBUcmFpbCIsCiAgICAgICBzdWJ0aXRsZSA9ICJUaW1lOiB7ZnJhbWVfYWxvbmd9IikgKwogIGdlb21faW1hZ2UoZGF0YSA9IG1hbGxvcmNhX2Jpa2VfZGF5NywKICAgICAgICAgICAgYWVzKHggPSBsb24sIHkgPSBsYXQsIGltYWdlID0gYmlrZV9pbWFnZV9saW5rKSwgCiAgICAgICAgICAgIHNpemUgPSAwLjA3NSkgKwogIHRyYW5zaXRpb25fcmV2ZWFsKHRpbWUpICsKICBzY2FsZV9jb2xvcl92aXJpZGlzX2Mob3B0aW9uID0gIm1hZ21hIikgKwogIHRoZW1lX21hcCgpICsKICB0aGVtZShsZWdlbmQuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSkKYGBgCiAgCmBgYHtyLCBldmFsPUZBTFNFfQphbmltX3NhdmUoImJpa2UxLmdpZiIpCmBgYAoKYGBge3IsIGVjaG89RkFMU0V9CmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKCJiaWtlMS5naWYiKQpgYGAKCkkgcGVyc29uYWxseSBwcmVmZXIgdGhpcyBhbmltYXRlZCBtYXAgb3ZlciB0aGUgc3RhdGljIG1hcCBhcyB0aGVyZSBhcmUgZGV0YWlscyB0aGF0IHdlIGFyZSBhYmxlIHRvIG9ic2VydmUgb24gdGhlIGFuaW1hdGVkIG1hcCwgc3VjaCBhcyB0aGUgZGlyZWN0aW9uIG9mIHRoZSBiaWtlIHJpZGUgYW5kIHRoZSBiaWtlIHRoYXQgcmVwcmVzZW50cyBMaXNhIG9uIGhlciBiaWtlLiBMYXN0bHksIGRvZXNuJ3QgYWxtb3N0IGFueSBhbmltYXRlZCBwbG90IGxvb2sgYmV0dGVyIHRoYW4gYSBzdGF0aWMgcGxvdD8gQXQgbGVhc3QgaW4gbXkgb3Bpbmlvbi4KCgogIDUuIEluIHRoaXMgZXhlcmNpc2UsIHlvdSBnZXQgdG8gbWVldCBteSBzaXN0ZXIsIEhlYXRoZXIhIFNoZSBpcyBhIHByb3VkIE1hYyBncmFkLCBjdXJyZW50bHkgd29ya3MgYXMgYSBEYXRhIFNjaWVudGlzdCBhdCAzTSB3aGVyZSBzaGUgdXNlcyBSIGV2ZXJ5ZGF5LCBhbmQgZm9yIGEgZmV3IHllYXJzICh3aGlsZSBzdGlsbCBob2xkaW5nIGEgZnVsbC10aW1lIGpvYikgc2hlIHdhcyBhIHBybyB0cmlhdGhsZXRlLiBZb3UgYXJlIGdvaW5nIHRvIG1hcCBvbmUgb2YgaGVyIHJhY2VzLiBUaGUgZGF0YSBmcm9tIGVhY2ggZGlzY2lwbGluZSBvZiB0aGUgSXJvbm1hbiA3MC4zIFBhbiBBbSBjaGFtcGlvbnNoaXBzLCBQYW5hbWEgaXMgaW4gYSBzZXBhcmF0ZSBmaWxlIC0gYHBhbmFtYV9zd2ltYCwgYHBhbmFtYV9iaWtlYCwgYW5kIGBwYW5hbWFfcnVuYC4gQ3JlYXRlIGEgc2ltaWxhciBtYXAgdG8gdGhlIG9uZSB5b3UgY3JlYXRlZCB3aXRoIG15IGN5Y2xpbmcgZGF0YS4gWW91IHdpbGwgbmVlZCB0byBtYWtlIHNvbWUgc21hbGwgY2hhbmdlczogMS4gY29tYmluZSB0aGUgZmlsZXMgKEhJTlQ6IGBiaW5kX3Jvd3MoKWAsIDIuIG1ha2UgdGhlIGxlYWRpbmcgZG90IGEgZGlmZmVyZW50IGNvbG9yIGRlcGVuZGluZyBvbiB0aGUgZXZlbnQgKGZvciBhbiBleHRyYSBjaGFsbGVuZ2UsIG1ha2UgaXQgYSBkaWZmZXJlbnQgaW1hZ2UgdXNpbmcgYGdlb21faW1hZ2UoKSEpLCAzLiBDSEFMTEVOR0UgKG9wdGlvbmFsKTogY29sb3IgYnkgc3BlZWQsIHdoaWNoIHlvdSB3aWxsIG5lZWQgdG8gY29tcHV0ZSBvbiB5b3VyIG93biBmcm9tIHRoZSBkYXRhLiBZb3UgY2FuIHJlYWQgSGVhdGhlcidzIHJhY2UgcmVwb3J0IFtoZXJlXShodHRwczovL2hlYXRoZXJsZW5kd2F5LmNvbS8yMDE2LzAyLzEwL2lyb25tYW4tNzAtMy1wYW4tYW1lcmljYW4tY2hhbXBpb25zaGlwcy1wYW5hbWEtcmFjZS1yZXBvcnQvKS4gU2hlIGlzIGFsc28gaW4gdGhlIE1hY2FsZXN0ZXIgQXRobGV0aWNzIFtIYWxsIG9mIEZhbWVdKGh0dHBzOi8vYXRobGV0aWNzLm1hY2FsZXN0ZXIuZWR1L2hvbm9ycy9oYWxsLW9mLWZhbWUvaGVhdGhlci1sZW5kd2F5LzE4NCkgYW5kIHN0aWxsIGhhcyByZWNvcmRzIGF0IHRoZSBwb29sLiAKICAKYGBge3IsIGV2YWw9RkFMU0V9CnRvdGFsX3RyYWlsIDwtIHBhbmFtYV9zd2ltICU+JQogIGJpbmRfcm93cyhsaXN0KHBhbmFtYV9ydW4sIHBhbmFtYV9iaWtlKSkgCiAgCnBhbmFtYV9tYXAgPC0gZ2V0X3N0YW1lbm1hcCgKICBiYm94ID0gYyhsZWZ0ID0gLTc5LjU2LCBib3R0b20gPSA4Ljg4LCByaWdodCA9IC03OS40MSwgdG9wID0gOS4wMDEpLAogIG1hcHR5cGUgPSAidGVycmFpbiIsCiAgem9vbSA9IDEzCikKCmdnbWFwKHBhbmFtYV9tYXApICsKICBnZW9tX3BvaW50KGRhdGEgPSB0b3RhbF90cmFpbCwgCiAgICAgICAgICAgICBhZXMoeCA9IGxvbiwgeSA9IGxhdCwgY29sb3IgPSBldmVudCwgc2hhcGUgPSBldmVudCksCiAgICAgICAgICAgICBzaXplID0gMikgKwogIGdlb21fcGF0aChkYXRhID0gdG90YWxfdHJhaWwsCiAgICAgICAgICAgIGFlcyh4ID0gbG9uLCB5ID0gbGF0LCBjb2xvciA9IGV2ZW50KSwKICAgICAgICAgICAgYWxwaGEgPSAwLjgsIHNpemUgPSAwLjUpICsKICBsYWJzKHRpdGxlID0gIklyb25tYW4gNzAuMyBQYW4gQW0gQ2hhbXBpb25zaGlwIiwKICAgICAgIHN1YnRpdGxlID0gIlRpbWU6IHtmcmFtZV9hbG9uZ30iKSArCiAgc2NhbGVfY29sb3JfdmlyaWRpc19kKG9wdGlvbiA9ICJtYWdtYSIpICsKICB0aGVtZV9tYXAoKSArCiAgdGhlbWUobGVnZW5kLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCkpICsKICB0cmFuc2l0aW9uX3JldmVhbCh0aW1lKQpgYGAKICAKYGBge3IsIGV2YWw9RkFMU0V9CmFuaW1fc2F2ZSgicGFuYW1hLmdpZiIpCmBgYAoKYGBge3IsIGVjaG89RkFMU0V9CmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKCJwYW5hbWEuZ2lmIikKYGBgCgojIyBDT1ZJRC0xOSBkYXRhCgogIDYuIEluIHRoaXMgZXhlcmNpc2UsIHlvdSBhcmUgZ29pbmcgdG8gcmVwbGljYXRlIG1hbnkgb2YgdGhlIGZlYXR1cmVzIGluIFt0aGlzXShodHRwczovL2FhdGlzaGIuY29tL2NvdmlkdHJlbmRzLz9yZWdpb249VVMpIHZpc3VhbGl6YXRpb24gYnkgQWl0aXNoIEJoYXRpYSBidXQgaW5jbHVkZSBhbGwgVVMgc3RhdGVzLiBSZXF1aXJlbWVudHM6CiAqIENyZWF0ZSBhIG5ldyB2YXJpYWJsZSB0aGF0IGNvbXB1dGVzIHRoZSBudW1iZXIgb2YgbmV3IGNhc2VzIGluIHRoZSBwYXN0IHdlZWsgKEhJTlQ6IHVzZSB0aGUgYGxhZygpYCBmdW5jdGlvbiB5b3UndmUgdXNlZCBpbiBhIHByZXZpb3VzIHNldCBvZiBleGVyY2lzZXMpLiBSZXBsYWNlIG1pc3NpbmcgdmFsdWVzIHdpdGggMCdzIHVzaW5nIGByZXBsYWNlX25hKClgLiAgCiAgKiBGaWx0ZXIgdGhlIGRhdGEgdG8gb21pdCByb3dzIHdoZXJlIHRoZSBjdW11bGF0aXZlIGNhc2UgY291bnRzIGFyZSBsZXNzIHRoYW4gMjAuICAKICAqIENyZWF0ZSBhIHN0YXRpYyBwbG90IHdpdGggY3VtdWxhdGl2ZSBjYXNlcyBvbiB0aGUgeC1heGlzIGFuZCBuZXcgY2FzZXMgaW4gdGhlIHBhc3QgNyBkYXlzIG9uIHRoZSB4LWF4aXMuIENvbm5lY3QgdGhlIHBvaW50cyBmb3IgZWFjaCBzdGF0ZSBvdmVyIHRpbWUuIEhJTlRTOiB1c2UgYGdlb21fcGF0aCgpYCBhbmQgYWRkIGEgYGdyb3VwYCBhZXN0aGV0aWMuICBQdXQgdGhlIHggYW5kIHkgYXhpcyBvbiB0aGUgbG9nIHNjYWxlIGFuZCBtYWtlIHRoZSB0aWNrIGxhYmVscyBsb29rIG5pY2UgLSBgc2NhbGVzOjpjb21tYWAgaXMgb25lIG9wdGlvbi4gVGhpcyBwbG90IHdpbGwgbG9vayBwcmV0dHkgdWdseSBhcyBpcy4KICAqIEFuaW1hdGUgdGhlIHBsb3QgdG8gcmV2ZWFsIHRoZSBwYXR0ZXJuIGJ5IGRhdGUuIERpc3BsYXkgdGhlIGRhdGUgYXMgdGhlIHN1YnRpdGxlLiBBZGQgYSBsZWFkaW5nIHBvaW50IHRvIGVhY2ggc3RhdGUncyBsaW5lIChgZ2VvbV9wb2ludCgpYCkgYW5kIGFkZCB0aGUgc3RhdGUgbmFtZSBhcyBhIGxhYmVsIChgZ2VvbV90ZXh0KClgIC0geW91IHNob3VsZCBsb29rIGF0IHRoZSBgY2hlY2tfb3ZlcmxhcGAgYXJndW1lbnQpLiAgCiAgKiBVc2UgdGhlIGBhbmltYXRlKClgIGZ1bmN0aW9uIHRvIGhhdmUgMjAwIGZyYW1lcyBpbiB5b3VyIGFuaW1hdGlvbiBhbmQgbWFrZSBpdCAzMCBzZWNvbmRzIGxvbmcuIAogICogQ29tbWVudCBvbiB3aGF0IHlvdSBvYnNlcnZlLgogIApgYGB7ciwgZXZhbD1GQUxTRX0KY292aWQxOSAlPiUKICBncm91cF9ieShzdGF0ZSkgJT4lCiAgbXV0YXRlKGxhZzcgPSBsYWcoY2FzZXMsIDcsIG9yZGVyX2J5ID0gZGF0ZSkpICU+JQogIHJlcGxhY2VfbmEobGlzdChsYWc3ID0gMCkpICU+JQogIG11dGF0ZShuZXdfY2FzZXNfcGFzdF93ZWVrID0gY2FzZXMgLSBsYWc3KSAlPiUKICBmaWx0ZXIoY2FzZXMgPj0gMjApICU+JQogIAogIGdncGxvdChhZXMoeCA9IGNhc2VzLCB5ID0gbmV3X2Nhc2VzX3Bhc3Rfd2VlaywgZ3JvdXAgPSBzdGF0ZSkpICsKICBnZW9tX3BvaW50KGNvbG9yID0gInJlZCIpICsKICBnZW9tX3BhdGgoY29sb3IgPSAibGlnaHQgYmx1ZSIpICsKICBnZW9tX3RleHQoYWVzKGxhYmVsID0gc3RhdGUpLCBjaGVja19vdmVybGFwID0gVFJVRSkgKwogIHNjYWxlX3hfbG9nMTAobGFiZWxzID0gc2NhbGVzOjpjb21tYSkgKwogIHNjYWxlX3lfbG9nMTAobGFiZWxzID0gc2NhbGVzOjpjb21tYSkgKwogIGxhYnMoCiAgICB0aXRsZSA9ICJUcmFqZWN0b3J5IG9mIFVTIENPVklELTE5IENvbmZpcm1lZCBDYXNlcyIsCiAgICB4ID0gIlRvdGFsIENvbmZpcm1lZCBDYXNlcyIsCiAgICB5ID0gIk5ldyBDb25maXJtZWQgQ2FzZXMgKGluIHRoZSBQYXN0IFdlZWspIiwKICAgIHN1YnRpdGxlID0gIkRhdGU6IHtmcmFtZV9hbG9uZ30iCiAgKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSArCiAgdHJhbnNpdGlvbl9yZXZlYWwoZGF0ZSkgLT4gY292aWQxOXRyYWplY3RvcnlfZ2dhbmltCgphbmltYXRlKGNvdmlkMTl0cmFqZWN0b3J5X2dnYW5pbSwKICAgICAgICBuZnJhbWVzID0gMjAwLAogICAgICAgIGR1cmF0aW9uID0gMzApCmBgYAogIAogIApgYGB7ciwgZXZhbD1GQUxTRX0KYW5pbV9zYXZlKCJjb3ZpZDIuZ2lmIikKYGBgCgogIApgYGB7ciwgZWNobz1GQUxTRX0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoImNvdmlkMi5naWYiKQpgYGAKICAKICBUaGVyZSBpcyBhIGxvdCB0byBvYnNlcnZlIGluIHRoaXMgdmlzdWFsaXphdGlvbiBhcyBhbGwgc3RhdGVzIGhhdmUgYmVlbiBpbmNsdWRlZC4gT25jZSBhZ2FpbiwgdGhpcyBwbG90IHZlcnkgY2xlYXJseSBzaG93cyB0aGUgZHJhc3RpYyBzdXJnZSBpbiBDT1ZJRC0xOSBjYXNlcyBpbiBOZXcgWW9yayBhbmQgTmV3IEplcnNleSBhdCB0aGUgc3RhcnQgb2YgdGhlIHBhbmRlbWljLiBXZSBhbHNvIG9ic2VydmUgaG93IGEgc3RhdGUgbGlrZSBWZXJtb250IGRvZXMgZXh0cmVtZWx5IHdlbGwgaW4gZGVhbGluZyB3aXRoIENPVklELTE5IGFzIHRoZWlyIG5ldyBjb25maXJtZWQgY2FzZXMgaW4gYSB3ZWVrIGRyb3AgcXVpY2tseSBpbiBKdW5lLCB0aGVuIHNlZSBhbiBpbmNyZWFzZSBhZ2FpbiBhdCB0aGUgZW5kIG9mIEp1bmUgYW5kIGZhbGwgYWdhaW4gYXQgdGhlIHN0YXJ0IG9mIEF1Z3VzdC4gRnVydGhlcm1vcmUsIHRoZWlyIHRvdGFsIG51bWJlciBvZiBjb25maXJtZWQgY2FzZXMgaXMgZXh0cmVtZWx5IGxvdywgYXJvdW5kIDEsMjAwIHBlb3BsZSBkdXJpbmcgdGhlIGVudGlyZSBwYW5kZW1pYy4gQWRkaXRpb25hbGx5LCB3ZSBhcmUgYWxzbyBhYmxlIHRvIG9ic2VydmUgdGhlIHJhcGlkIGluY3JlYXNlIG9mIGNhc2VzIGluIGJvdGggRmxvcmlkYSBhbmQgVGV4YXMgaW4gSnVseSBnb2luZyB0aHJvdWdoIGJvdGggQXVndXN0IGFuZCBTZXB0ZW1iZXIuIAogIAogIDcuIEluIHRoaXMgZXhlcmNpc2UgeW91IHdpbGwgYW5pbWF0ZSBhIG1hcCBvZiB0aGUgVVMsIHNob3dpbmcgaG93IGN1bXVsYXRpdmUgQ09WSUQtMTkgY2FzZXMgcGVyIDEwLDAwMCByZXNpZGVudHMgaGFzIGNoYW5nZWQgb3ZlciB0aW1lLiBUaGlzIGlzIHNpbWlsYXIgdG8gZXhlcmNpc2VzIDExICYgMTIgZnJvbSB0aGUgcHJldmlvdXMgZXhlcmNpc2VzLCB3aXRoIHRoZSBhZGRlZCBhbmltYXRpb24hIFNvLCBpbiB0aGUgZW5kLCB5b3Ugc2hvdWxkIGhhdmUgc29tZXRoaW5nIGxpa2UgdGhlIHN0YXRpYyBtYXAgeW91IG1hZGUgdGhlcmUsIGJ1dCBhbmltYXRlZCBvdmVyIGFsbCB0aGUgZGF5cy4gUHV0IGRhdGUgaW4gdGhlIHN1YnRpdGxlLiBDb21tZW50IG9uIHdoYXQgeW91IHNlZS4KICAKYGBge3J9CmNlbnN1c19wb3BfZXN0XzIwMTggPC0gcmVhZF9jc3YoImh0dHBzOi8vd3d3LmRyb3Bib3guY29tL3MvNnR4d3YzYjRuZzdwZXBlL3VzX2NlbnN1c18yMDE4X3N0YXRlX3BvcF9lc3QuY3N2P2RsPTEiKSAlPiUgCiAgc2VwYXJhdGUoc3RhdGUsIGludG8gPSBjKCJkb3QiLCJzdGF0ZSIpLCBleHRyYSA9ICJtZXJnZSIpICU+JSAKICBzZWxlY3QoLWRvdCkgJT4lIAogIG11dGF0ZShzdGF0ZSA9IHN0cl90b19sb3dlcihzdGF0ZSkpCmBgYAoKICAKICAKYGBge3IsIGV2YWw9RkFMU0V9CmNvdmlkMTlfcG9wdWxhdGlvbiA8LQogIGNvdmlkMTkgJT4lIAogIG11dGF0ZShzdGF0ZSA9IHN0cl90b19sb3dlcihzdGF0ZSkpICU+JQogIGxlZnRfam9pbihjZW5zdXNfcG9wX2VzdF8yMDE4LAogICAgICAgICAgICBieSA9ICJzdGF0ZSIpICU+JSAKICBncm91cF9ieShzdGF0ZSwgZXN0X3BvcF8yMDE4LCBkYXRlKSAlPiUKICBzdW1tYXJpemUoY3VtdWxhdGl2ZV9jYXNlcyA9IG1heChjYXNlcykpICU+JQogIG11dGF0ZShjYXNlc19wZXJfMTAwMDAgPSAoY3VtdWxhdGl2ZV9jYXNlcy9lc3RfcG9wXzIwMTgpKjEwMDAwKQoKc3RhdGVzX21hcCA8LSBtYXBfZGF0YSgic3RhdGUiKQoKY292aWRfbWFwIDwtIGNvdmlkMTlfcG9wdWxhdGlvbiAlPiUgCiAgbXV0YXRlKHN0YXRlID0gc3RyX3RvX2xvd2VyKHN0YXRlKSwgd2Vla2RheSA9IHdkYXkoZGF0ZSwgbGFiZWw9VFJVRSkpICU+JQogIGZpbHRlcih3ZWVrZGF5ID09ICJGcmkiKSAlPiUKICBnZ3Bsb3QoKSArCiAgZ2VvbV9tYXAobWFwID0gc3RhdGVzX21hcCwKICAgICAgICAgICBhZXMobWFwX2lkID0gc3RhdGUsIGZpbGwgPSBjYXNlc19wZXJfMTAwMDAsIGdyb3VwID0gZGF0ZSkpICsKICBleHBhbmRfbGltaXRzKHggPSBzdGF0ZXNfbWFwJGxvbmcsIHkgPSBzdGF0ZXNfbWFwJGxhdCkgKyAKICBsYWJzKHRpdGxlID0gIkN1bXVsYXRpdmUgQ09WSUQtMTkgY2FzZXMgcGVyIDEwLDAwMCBwZW9wbGUgaW4gdGhlIFVuaXRlZCBTdGF0ZXMiKSArCiAgdGhlbWUobGVnZW5kLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCkpICsgCiAgdGhlbWVfbWFwKCkgKwogIHNjYWxlX2ZpbGxfdmlyaWRpc19jKCkgKwogIHRyYW5zaXRpb25fc3RhdGVzKGRhdGUsIHRyYW5zaXRpb25fbGVuZ3RoID0gMCkgKwogIGxhYnMoc3VidGl0bGUgPSAiTW92aW5nIHRvIHtuZXh0X3N0YXRlfSIpCgphbmltYXRlKGNvdmlkX21hcCwgZHVyYXRpb24gPSAzMCkKYGBgCgpgYGB7ciwgZXZhbD1GQUxTRX0KYW5pbV9zYXZlKCJjb3ZpZC5naWYiLCBjb3ZpZF9tYXApCmBgYAogIAoKYGBge3IsIGVjaG89RkFMU0V9CmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKCJjb3ZpZC5naWYiKQpgYGAKIAogV2UgY2xlYXJseSBzZWUgdGhlIHNhZCByZWFsaXR5IG9mIENPVklELTE5IGluIHRoZSBVbml0ZWQgU3RhdGVzLiBJdCBzdGFydHMgb2ZmIHdpdGggYSByYXBpZGx5IHJpc2luZyBudW1iZXIgb2YgY2FzZXMgcGVyIDEwLDAwMCBwZW9wbGUgaW4gTmV3IFlvcmssIExvdXNpYW5hLCBhbmQgQXJpem9uYS4gU2hvcnRseSBhZnRlciwgdGhlIHdob2xlIGNvdW50cnkgZm9sbG93cyB3aXRoIGFsbW9zdCBldmVyeSBzdGF0ZSBoYXZpbmcgbW9yZSB0aGFuIDMwMCBDT1ZJRC0xOSBjYXNlcyBwZXIgc3RhdGUgZXhjZXB0IGZvciBhIGNvdXBsZSBzdGF0ZXMsIHN1Y2ggYXMgVmVybW9udCwgTWFpbmUsIGFuZCBPcmVnb24uIFdlIHVsdGltYXRlbHkgc2VlIHRoZSBVbml0ZWQgU3RhdGVzIGxpZ2h0IHVwIGluIGdyZWVuIGFuZCB5ZWxsb3cgd2hpY2ggbWVhbnMgdGhhdCBhbG1vc3QgZXZlcnkgc3RhdGUgaGFzIGNsb3NlIHRvIDIwMC0zMDAgY2FzZXMgcGVyIDEwLDAwMCBwZW9wbGUuCiAgCiMjIEdpdEh1YiBsaW5rCgogIDguIEJlbG93LCBwcm92aWRlIGEgbGluayB0byB5b3VyIEdpdEh1YiBwYWdlIHdpdGggdGhpcyBzZXQgb2YgV2Vla2x5IEV4ZXJjaXNlcy4gU3BlY2lmaWNhbGx5LCBpZiB0aGUgbmFtZSBvZiB0aGUgZmlsZSBpcyAwNV9leGVyY2lzZXMuUm1kLCBwcm92aWRlIGEgbGluayB0byB0aGUgMDVfZXhlcmNpc2VzLm1kIGZpbGUsIHdoaWNoIGlzIHRoZSBvbmUgdGhhdCB3aWxsIGJlIG1vc3QgcmVhZGFibGUgb24gR2l0SHViLiBJZiB0aGF0IGZpbGUgaXNuJ3QgdmVyeSByZWFkYWJsZSwgdGhlbiBwcm92aWRlIGEgbGluayB0byB5b3VyIG1haW4gR2l0SHViIHBhZ2UuCiAgCgoqKkRJRCBZT1UgUkVNRU1CRVIgVE8gVU5DT01NRU5UIFRIRSBPUFRJT05TIEFUIFRIRSBUT1A/KioK